﻿#include "elk_browser_client.h"
#include "elk_cef_types_from.h"

#define MENU_ID_SHOW_DETAILS        MENU_ID_USER_FIRST + 1
#define MENU_ID_DOWNLOAD_MEDIA      MENU_ID_USER_FIRST + 2
#define MENU_ID_VIEW_DISPLAY_TEXT   MENU_ID_USER_FIRST + 3

class ElkPageSourceVisitor : public CefStringVisitor
{
public:
    ElkPageSourceVisitor(const QString frame, ElkWebDelegate *delegate) :
        frame_(frame),
        delegate_(delegate)
    {
        LOG(INFO) << "ElkPageSourceVisitor()";
    }
    ~ElkPageSourceVisitor() {
        LOG(INFO) << "~ElkPageSourceVisitor()";
    }
    virtual void Visit(const CefString &string) OVERRIDE {
        emit delegate_->pageSourceVisited(frame_, string.ToString().c_str());
    }
private:
    QString frame_;
    ElkWebDelegate *delegate_;
    IMPLEMENT_REFCOUNTING(ElkPageSourceVisitor);
};

class ElkDisplayTextVisitor : public CefStringVisitor
{
public:
    ElkDisplayTextVisitor(const QString frame, ElkWebDelegate *delegate) :
        frame_(frame),
        delegate_(delegate)
    {
        LOG(INFO) << "ElkDisplayTextVisitor()";
    }
    ~ElkDisplayTextVisitor() {
        LOG(INFO) << "~ElkDisplayTextVisitor()";
    }
    virtual void Visit(const CefString &string) OVERRIDE {
        emit delegate_->displayTextVisited(frame_, string.ToString().c_str());
    }
private:
    QString frame_;
    ElkWebDelegate *delegate_;
    IMPLEMENT_REFCOUNTING(ElkDisplayTextVisitor);
};

ElkBrowserClient::ElkBrowserClient(ElkWebDelegate *delegate,
                                   WId parent, const QString &title) :
    delegate_(delegate)
{
    LOG(INFO) << "ElkBrowserClient()";

    CefWindowInfo windowInfo;
    windowInfo.SetAsPopup((CefWindowHandle)parent, title.toStdString().c_str());

    CefBrowserSettings browserSettings;
    //browserSettings.image_loading = STATE_DISABLED;
    browserSettings.background_color = CefColorSetARGB(255, 255, 245, 247);

    //此方式创建的 CefBrowser 需要确保父窗口在 CefShutDown 调用前释放，否则会在 CefShutDown 调用时造成崩溃现象或导致进程无法正常退出
    CefBrowserHost::CreateBrowser(windowInfo, this, CefString(), browserSettings, nullptr, nullptr);
}

ElkBrowserClient::ElkBrowserClient(ElkWebDelegate *delegate,
                                   WId parent, int x, int y, int width, int height) :
    delegate_(delegate)
{
    LOG(INFO) << "ElkBrowserClient()";

    RECT rect;
    rect.top = y;
    rect.left = x;
    rect.right = width - 1;
    rect.bottom = height - 1;

    CefWindowInfo windowInfo;
    windowInfo.SetAsChild((CefWindowHandle)parent, rect);

    CefBrowserSettings browserSettings;
    //browserSettings.image_loading = STATE_DISABLED;
    browserSettings.background_color = CefColorSetARGB(255, 255, 245, 247);

    //此方式创建的 CefBrowser 需要确保父窗口在 CefShutDown 调用前释放，否则会在 CefShutDown 调用时造成崩溃现象或导致进程无法正常退出
    CefBrowserHost::CreateBrowser(windowInfo, this, CefString(), browserSettings, nullptr, nullptr);
}

ElkBrowserClient::~ElkBrowserClient()
{
    LOG(INFO) << "~ElkBrowserClient()";
}

void ElkBrowserClient::SetFocus(bool focus)
{
    if (browser_) {
        browser_->GetHost()->SetFocus(focus);
    }
}

void ElkBrowserClient::SetPosition(int x, int y, int width, int height)
{
    if (browser_) {
        CefWindowHandle hwnd = browser_->GetHost()->GetWindowHandle();
        ::MoveWindow(hwnd, x, y, width, height, TRUE);
    }
}

void ElkBrowserClient::Load(const QString &url)
{
    if (browser_) {
        browser_->GetMainFrame()->LoadURL(url.toStdString().c_str());
    }
}

void ElkBrowserClient::StopLoad()
{
    if (browser_) {
        browser_->StopLoad();
    }
}

void ElkBrowserClient::Reload()
{
    if (browser_) {
        browser_->Reload();
    }
}

void ElkBrowserClient::ReloadIgnoreCache()
{
    if (browser_) {
        browser_->ReloadIgnoreCache();
    }
}

void ElkBrowserClient::GoBack()
{
    if (browser_) {
        browser_->GoBack();
    }
}

bool ElkBrowserClient::CanGoBack() const
{
    if (browser_) {
        return browser_->CanGoBack();
    }
    return false;
}

void ElkBrowserClient::GoForward()
{
    if (browser_) {
        browser_->GoForward();
    }
}

bool ElkBrowserClient::CanGoForward() const
{
    if (browser_) {
        return browser_->CanGoForward();
    }
    return false;
}

bool ElkBrowserClient::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
                                                CefRefPtr<CefFrame> frame,
                                                CefProcessId source_process,
                                                CefRefPtr<CefProcessMessage> message)
{
    ALLOW_UNUSED_LOCAL(browser);
    ALLOW_UNUSED_LOCAL(frame);
    ALLOW_UNUSED_LOCAL(source_process);
    ALLOW_UNUSED_LOCAL(message);
    return false;
}

void ElkBrowserClient::OnAudioStreamStarted(CefRefPtr<CefBrowser> browser,
                                            const CefAudioParameters &params,
                                            int channels)
{
    LOG(INFO) << "OnAudioStreamStarted()" << ":browser" << browser->GetIdentifier();
    LOG(INFO) << "Channels: " << channels;
    LOG(INFO) << "ChannelLayout: " << params.channel_layout;
    LOG(INFO) << "FramesPerBuffer: " << params.frames_per_buffer;
    LOG(INFO) << "SampleRate: " << params.sample_rate;
}

void ElkBrowserClient::OnAudioStreamPacket(CefRefPtr<CefBrowser> browser,
                                           const float **data,
                                           int frames,
                                           int64 pts)
{
    ALLOW_UNUSED_LOCAL(browser);
    ALLOW_UNUSED_LOCAL(data);
    ALLOW_UNUSED_LOCAL(frames);
    ALLOW_UNUSED_LOCAL(pts);
}

void ElkBrowserClient::OnAudioStreamStopped(CefRefPtr<CefBrowser> browser)
{
    LOG(INFO) << "OnAudioStreamStopped()" << ":browser" << browser->GetIdentifier();
}

void ElkBrowserClient::OnAudioStreamError(CefRefPtr<CefBrowser> browser,
                                          const CefString &message)
{
    LOG(INFO) << "OnAudioStreamError()" << ":browser" << browser->GetIdentifier();
    LOG(INFO) << message;
}

void ElkBrowserClient::OnBeforeContextMenu(CefRefPtr<CefBrowser> browser,
                                           CefRefPtr<CefFrame> frame,
                                           CefRefPtr<CefContextMenuParams> params,
                                           CefRefPtr<CefMenuModel> model)
{
    ALLOW_UNUSED_LOCAL(browser);
    ALLOW_UNUSED_LOCAL(frame);

    if (!params->IsEditable()) {
        model->AddSeparator();
        model->AddItem(MENU_ID_SHOW_DETAILS, "Show details");
        model->AddItem(MENU_ID_VIEW_DISPLAY_TEXT, "View display text");

        if ((params->GetMediaType() == CM_MEDIATYPE_IMAGE) &&
                !params->GetSourceUrl().empty()) {
            model->AddItem(MENU_ID_DOWNLOAD_MEDIA, "Download image");
        }
        else if ((params->GetMediaType() == CM_MEDIATYPE_VIDEO) &&
                !params->GetSourceUrl().empty()) {
            model->AddItem(MENU_ID_DOWNLOAD_MEDIA, "Download video");
        }
        else if ((params->GetMediaType() == CM_MEDIATYPE_AUDIO) &&
                !params->GetSourceUrl().empty()) {
            model->AddItem(MENU_ID_DOWNLOAD_MEDIA, "Download audio");
        }
        else if ((params->GetMediaType() == CM_MEDIATYPE_FILE) &&
                !params->GetSourceUrl().empty()) {
            model->AddItem(MENU_ID_DOWNLOAD_MEDIA, "Download file");
        }
    }
}

bool ElkBrowserClient::OnContextMenuCommand(CefRefPtr<CefBrowser> browser,
                                            CefRefPtr<CefFrame> frame,
                                            CefRefPtr<CefContextMenuParams> params,
                                            int command_id,
                                            CefContextMenuHandler::EventFlags event_flags)
{
    ALLOW_UNUSED_LOCAL(browser);
    ALLOW_UNUSED_LOCAL(event_flags);

    ElkContextMenuDetails details;
    details.contextMenuType = ElkCefTypesFrom::contextMenuType(params->GetTypeFlags());
    details.pageUrl = params->GetPageUrl().ToString().c_str();
    details.frameUrl = params->GetFrameUrl().ToString().c_str();
    details.frameCharset = params->GetFrameCharset().ToString().c_str();
    details.linkUrl = params->GetLinkUrl().ToString().c_str();
    details.contextMenuMediaType = ElkCefTypesFrom::contextMenuMediaType(params->GetMediaType());
    details.mediaSourceUrl = params->GetSourceUrl().ToString().c_str();
    details.mediaTitleText = params->GetTitleText().ToString().c_str();
    details.selectionText = params->GetSelectionText().ToString().c_str();

    QString frame_name(frame->GetName().ToString().c_str());
    if (command_id == MENU_ID_SHOW_DETAILS) {
        emit delegate_->contextMenuShowDetails(frame_name, details);
    } else if (command_id == MENU_ID_DOWNLOAD_MEDIA) {
        emit delegate_->contextMenuDownloadMedia(frame_name, details);
    } else if (command_id == MENU_ID_VIEW_DISPLAY_TEXT) {
        emit delegate_->contextMenuViewDisplayText(frame_name, details);
        frame->GetText(CefRefPtr<CefStringVisitor>(new ElkDisplayTextVisitor(frame_name, delegate_)));
    } else if (command_id == MENU_ID_VIEW_SOURCE) {
        emit delegate_->contextMenuViewPageSource(frame_name, details);
        frame->GetSource(CefRefPtr<CefStringVisitor>(new ElkPageSourceVisitor(frame_name, delegate_)));
    } else {
        return false;
    }
    return true;
}

void ElkBrowserClient::OnAddressChange(CefRefPtr<CefBrowser> browser,
                                       CefRefPtr<CefFrame> frame,
                                       const CefString &url)
{
    ALLOW_UNUSED_LOCAL(browser);
    if (frame->IsMain()) {
        emit delegate_->urlChanged(url.ToString().c_str());
    }
}

void ElkBrowserClient::OnTitleChange(CefRefPtr<CefBrowser> browser,
                                     const CefString &title)
{
    ALLOW_UNUSED_LOCAL(browser);
    emit delegate_->titleChanged(title.ToString().c_str());
}

void ElkBrowserClient::OnFullscreenModeChange(CefRefPtr<CefBrowser> browser,
                                              bool fullscreen)
{
    ALLOW_UNUSED_LOCAL(browser);
    emit delegate_->fullscreenModeChanged(fullscreen);
}

void ElkBrowserClient::OnLoadingProgressChange(CefRefPtr<CefBrowser> browser,
                                               double progress)
{
    ALLOW_UNUSED_LOCAL(browser);
    emit delegate_->loadingProgressChanged(progress);
}

void ElkBrowserClient::OnStatusMessage(CefRefPtr<CefBrowser> browser,
                                       const CefString &value)
{
    ALLOW_UNUSED_LOCAL(browser);
    emit delegate_->statusMessage(value.ToString().c_str());
}

bool ElkBrowserClient::OnConsoleMessage(CefRefPtr<CefBrowser> browser,
                                        cef_log_severity_t level,
                                        const CefString &message,
                                        const CefString &source,
                                        int line)
{
    ALLOW_UNUSED_LOCAL(browser);
    ALLOW_UNUSED_LOCAL(level);
    ALLOW_UNUSED_LOCAL(source);
    ALLOW_UNUSED_LOCAL(line);
    emit delegate_->consoleMessage(message.ToString().c_str());
    return true; //默认取消控制台消息输出
}

void ElkBrowserClient::OnBeforeDownload(CefRefPtr<CefBrowser> browser,
                                        CefRefPtr<CefDownloadItem> download_item,
                                        const CefString &suggested_name,
                                        CefRefPtr<CefBeforeDownloadCallback> callback)
{
    ALLOW_UNUSED_LOCAL(browser);
    ALLOW_UNUSED_LOCAL(callback);

    std::string url = download_item->GetURL().ToString();
    if (url.find("blob") == 0) {
        callback->Continue(CefString(), true);
    } else {
        emit delegate_->downloadRequested(url.c_str(), suggested_name.ToString().c_str());
    }
}

void ElkBrowserClient::OnDownloadUpdated(CefRefPtr<CefBrowser> browser,
                                         CefRefPtr<CefDownloadItem> download_item,
                                         CefRefPtr<CefDownloadItemCallback> callback)
{
    ALLOW_UNUSED_LOCAL(browser);
    ALLOW_UNUSED_LOCAL(download_item);

    std::string url = download_item->GetURL().ToString();
    if (url.find("blob") == 0) {

    } else {
        callback->Cancel(); //防止自动触发下载逻辑
    }
}

void ElkBrowserClient::OnGotFocus(CefRefPtr<CefBrowser> browser)
{
    ALLOW_UNUSED_LOCAL(browser);
    emit delegate_->gotFocus();
}

void ElkBrowserClient::OnTakeFocus(CefRefPtr<CefBrowser> browser,
                                   bool next)
{
    ALLOW_UNUSED_LOCAL(browser);
    ALLOW_UNUSED_LOCAL(next);
    emit delegate_->takeFocus();
}

bool ElkBrowserClient::OnSetFocus(CefRefPtr<CefBrowser> browser,
                                  CefFocusHandler::FocusSource source)
{
    ALLOW_UNUSED_LOCAL(browser);
    ALLOW_UNUSED_LOCAL(source);
    return false;
}

bool ElkBrowserClient::OnBeforePopup(CefRefPtr<CefBrowser> browser,
                                     CefRefPtr<CefFrame> frame,
                                     const CefString &target_url,
                                     const CefString &target_frame_name,
                                     CefLifeSpanHandler::WindowOpenDisposition target_disposition,
                                     bool user_gesture,
                                     const CefPopupFeatures &popupFeatures,
                                     CefWindowInfo &windowInfo,
                                     CefRefPtr<CefClient> &client,
                                     CefBrowserSettings &settings,
                                     CefRefPtr<CefDictionaryValue> &extra_info,
                                     bool *no_javascript_access)
{
    ALLOW_UNUSED_LOCAL(browser);
    ALLOW_UNUSED_LOCAL(target_frame_name);
    ALLOW_UNUSED_LOCAL(target_disposition);
    ALLOW_UNUSED_LOCAL(user_gesture);
    ALLOW_UNUSED_LOCAL(popupFeatures);
    ALLOW_UNUSED_LOCAL(windowInfo);
    ALLOW_UNUSED_LOCAL(client);
    ALLOW_UNUSED_LOCAL(settings);
    ALLOW_UNUSED_LOCAL(extra_info);
    ALLOW_UNUSED_LOCAL(no_javascript_access);
    frame->LoadURL(target_url);
    return true; //默认在当前页面显示
}

void ElkBrowserClient::OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
    LOG(INFO) << "OnAfterCreated()" << ":browser" << browser->GetIdentifier();
    browser_ = browser;
}

void ElkBrowserClient::OnBeforeClose(CefRefPtr<CefBrowser> browser)
{
    LOG(INFO) << "OnBeforeClose()" << ":browser" << browser->GetIdentifier();
    browser_ = nullptr;
}

void ElkBrowserClient::OnLoadStart(CefRefPtr<CefBrowser> browser,
                                   CefRefPtr<CefFrame> frame,
                                   CefLoadHandler::TransitionType transition_type)
{
    ALLOW_UNUSED_LOCAL(browser);
    ALLOW_UNUSED_LOCAL(transition_type);
    emit delegate_->loadStart(frame->GetName().ToString().c_str());
}

void ElkBrowserClient::OnLoadEnd(CefRefPtr<CefBrowser> browser,
                                 CefRefPtr<CefFrame> frame,
                                 int httpStatusCode)
{
    ALLOW_UNUSED_LOCAL(browser);
    emit delegate_->loadEnd(frame->GetName().ToString().c_str(), httpStatusCode);
}

bool ElkBrowserClient::OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
                                      CefRefPtr<CefFrame> frame,
                                      CefRefPtr<CefRequest> request,
                                      bool user_gesture,
                                      bool is_redirect)
{
    ALLOW_UNUSED_LOCAL(browser);
    ALLOW_UNUSED_LOCAL(frame);

    ElkRequestDetails details;
    ElkCefTypesFrom::fromRequest(request, details);
    emit delegate_->beforeBrowser(frame->GetName().ToString().c_str(), details,
                                  user_gesture, is_redirect);
    return false;
}

CefRefPtr<CefResourceRequestHandler> ElkBrowserClient::GetResourceRequestHandler(CefRefPtr<CefBrowser> browser,
                                                                                 CefRefPtr<CefFrame> frame,
                                                                                 CefRefPtr<CefRequest> request,
                                                                                 bool is_navigation,
                                                                                 bool is_download,
                                                                                 const CefString &request_initiator,
                                                                                 bool &disable_default_handling)
{
    ALLOW_UNUSED_LOCAL(browser);
    ALLOW_UNUSED_LOCAL(is_navigation);
    ALLOW_UNUSED_LOCAL(is_download);
    ALLOW_UNUSED_LOCAL(request_initiator);
    ALLOW_UNUSED_LOCAL(disable_default_handling);

    ElkRequestDetails details;
    ElkCefTypesFrom::fromRequest(request, details);
    emit delegate_->resourceRequested(frame->GetName().ToString().c_str(), details);
    return nullptr;
}
